/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package ai;

import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;

/**
 *
 * @author root
 */
public class ParseNode {

    ParseNode(String p_Label){
        mLabel = p_Label;
        mChildList = new LinkedList<ParseNode>();
        mDefaultStartIndex = 0;
        mDefaultEndIndex = 0;
        mSemExp = null;
    }

    ParseNode(String p_Label, int pStartIndex, int pEndIndex , SemanticExp pSemExp){
        mLabel = p_Label;
        mChildList = new LinkedList<ParseNode>();
        mDefaultStartIndex = pStartIndex;
        mDefaultEndIndex = pEndIndex;
        mSemExp = pSemExp;
    }
    
    public void addNode(ParseNode p_Node){
        mChildList.add(p_Node);
    }
    
    public int getStartIndex(){
        if(mChildList.isEmpty()){
            return mDefaultStartIndex;
        }
        else {
            return mChildList.peekFirst().getStartIndex();
        }
    }
    public int getEndIndex(){
        if(mChildList.isEmpty()){
            return mDefaultEndIndex;
        }
        else {
            return mChildList.peekLast().getEndIndex();
        }
    }
    
    public void executeSemantics(KnowledgeBase pKB, QueryForm pQF) throws ParseException{
        LinkedList<SemanticExp> lSemExps = fetchSemantics(pKB);
        System.out.println("Semantics:\n" + lSemExps);
        for(SemanticExp se : lSemExps){
            pQF.execute(se);
        }
    }
    
    private LinkedList<SemanticExp> fetchSemantics(KnowledgeBase pKB) throws ParseException{
        LinkedList<LinkedList<SemanticExp>> lArgs = new LinkedList<LinkedList<SemanticExp>>();
        
        for(ParseNode p : mChildList){
            LinkedList<SemanticExp> lChildList = p.fetchSemantics(pKB);
            if(!lChildList.isEmpty()){
                lArgs.add(lChildList);
            }
        }
        
        LinkedList<SemanticExp> lNewSemList = new LinkedList<SemanticExp>();
        if(mSemExp != null){
            if(SemanticExp.SEMEXP == mSemExp.getType()){
                // Post process the arguments to fit the parameters.
                LinkedList<LinkedList<SemanticExp>> lNewArgs = new LinkedList<LinkedList<SemanticExp>>();
                int lCurrentIndex = 0;
                int lCurrentGroupIndex = 0;
                int lParamIndex = 0;
                
                for(SemanticExp.Input in : mSemExp.getInputs()){
                    LinkedList<SemanticExp> lNewList = new LinkedList<SemanticExp>();
                    if(in.mGroup){
                        for(int i = lCurrentGroupIndex; i <= lParamIndex && lCurrentIndex < lArgs.size(); i++){
                            InputExp ie = getInputExp(lCurrentIndex, lArgs);
                            lCurrentGroupIndex = ie.mCurrentGroupIndex;
                            lNewList.add(ie.mSE);
                            lCurrentIndex++;
                        }
                    }
                    else{
                        InputExp ie = getInputExp(lCurrentIndex, lArgs);
                        lCurrentGroupIndex = ie.mCurrentGroupIndex;
                        lNewList.add(ie.mSE);
                        lCurrentIndex++;
                    }
                    lNewArgs.add(lNewList);
                    lParamIndex++;
                }
                lArgs = lNewArgs;
                
                for(SemanticExp se : mSemExp.getOutputs()){
                    LinkedList<SemanticExp> lSubtitutions = substituteSemanticVars(pKB, se, lArgs);
                    for (SemanticExp se2 : lSubtitutions){
                        lNewSemList.add(se2);
                    }
                }
            }
            else{
                LinkedList<SemanticExp> lSubtitutions = substituteSemanticVars(pKB, mSemExp, lArgs);
                for (SemanticExp se : lSubtitutions){
                    lNewSemList.add(se);
                }
            }
        }
        else {
            for(LinkedList<SemanticExp> lList : lArgs){
                for(SemanticExp se : lList){
                    lNewSemList.add(se);
                }
            }
        }
        
        return lNewSemList;
    }

    private LinkedList<SemanticExp> substituteSemanticVars(KnowledgeBase pKB, SemanticExp pSemExp, LinkedList<LinkedList<SemanticExp>> pArgs) throws ParseException{
        LinkedList<SemanticExp> lReturnValue = new LinkedList<SemanticExp>();
        
        if(pSemExp != null){
            if(SemanticExp.CONST == pSemExp.getType()){
                lReturnValue.add(pSemExp);
            }
            else if(SemanticExp.LABEL == pSemExp.getType()){
                if(pKB.isWordCategory(pSemExp.getName()) ){
                    ParseNode lPNode = findChildNode(pSemExp.getName());
                    if(lPNode.getWord() != null){
                        lReturnValue.add( new SemanticExp(lPNode.getWord(), SemanticExp.CONST));
                    }
                    else{
                        throw new ParseException("ParseNode.substituteSemanticVars: Label " + pSemExp.getName() + "is not associated with a word.");
                    }
                    
                }
                else{
                    throw new ParseException("ParseNode.substituteSemanticVars: Label " + pSemExp.getName() + "is not a word category.");
                }
            }
            else if(SemanticExp.LABELCONV == pSemExp.getType()){
                if(pKB.isWordCategory(pSemExp.getName()) && pKB.isWordCategory(pSemExp.getSecondLabel())){
                    ParseNode lPNode = findChildNode(pSemExp.getName());
                    if(lPNode.getWord() != null){
                        HashMap<String, String> lMap = pKB.findTranslationTable(pSemExp.getName(), pSemExp.getSecondLabel());
                        if (lMap != null){
                            lReturnValue.add(new SemanticExp(lMap.get(lPNode.getWord()), SemanticExp.CONST));
                        }
                        else{
                            throw new ParseException("ParseNode.substituteSemanticVars: Invalid label convertion! Converting " + pSemExp.getName() + " to " + pSemExp.getSecondLabel() + ".");
                        }
                    }
                    else{
                        throw new ParseException("ParseNode.substituteSemanticVars: Label " + pSemExp.getName() + "is not associated with a word.");
                    }
                    
                }
                else{
                    throw new ParseException("ParseNode.substituteSemanticVars: Label " + pSemExp.getName() + "is not a word category.");
                }
            }
            else if(SemanticExp.PARAM == pSemExp.getType()){
                if(pSemExp.getParamGroup()){
                    lReturnValue = pArgs.get(pSemExp.getParamIndex());
                }
                else{
                    lReturnValue.add(getInputExp(pSemExp.getParamIndex(), pArgs).mSE);
                }
            }
            else if(SemanticExp.FUNC == pSemExp.getType()){
                SemanticExp lClone = pSemExp.halfClone();

                for(SemanticExp se : pSemExp.getOutputs()){
                    LinkedList<SemanticExp> lList = substituteSemanticVars(pKB, se, pArgs);
                    for(SemanticExp se2 : lList){
                        lClone.addOperation(se2);
                    }
                }
                
                // If function name is a parameter.
                if(lClone.getParamIndex() >= 0){
                    SemanticExp lSE = getInputExp(pSemExp.getParamIndex(), pArgs).mSE;
                    lClone.setName(lSE.getName());
                    Iterator lIter = lSE.getOutputs().descendingIterator();
                    while(lIter.hasNext()){
                        lClone.getOutputs().addFirst((SemanticExp)lIter.next());
                    }
                }
                
                lReturnValue.add(lClone);
            }
            else{
                throw new ParseException("ParseNode.substituteSemanticVars: Invalid semantic expression type!");
            }
        }
        return lReturnValue;
    }
    
    private InputExp getInputExp(int pIndex, LinkedList<LinkedList<SemanticExp>> pInputs){
        int lArgCount = 0;
        int lGroupIndex = 0;
        for(LinkedList<SemanticExp> l : pInputs){
            if (lArgCount + l.size() > pIndex){
                return new InputExp(l.get(pIndex - lArgCount), lGroupIndex);
            }
            lArgCount += l.size();
            lGroupIndex++;
        }
        return null;
    }
    
    private ParseNode findChildNode(String pLabel) throws ParseException{
        for(ParseNode p : mChildList){
            if(p.getLabel().equals(pLabel)){
                return p;
            }
        }
        throw new ParseException("ParseNode.findChildNode: Cannot find label " + pLabel );
    }
 
    @Override
    public String toString(){
        String lString = replicate(' ', indentation) + mLabel;
        if(mWord != null){
            lString += " \"" + mWord + "\" ";
        }
        if(mSemExp != null){
            lString += mSemExp;
        }
        lString += "(" + mDefaultStartIndex + ", " + mDefaultEndIndex + ")\n" + replicate(' ', indentation) + "{\n";
        indentation += 4;
        for(ParseNode n : mChildList){
            lString = lString + n.toString() + "\n";
        }
        indentation -= 4;
        return lString + replicate(' ', indentation) + "}";
    }
    
    @Override
    public Object clone(){
        ParseNode lNode = new ParseNode(mLabel);
        lNode.mChildList = (LinkedList<ParseNode>)mChildList.clone();
        return lNode;
    }
    
    public String getLabel(){
        return mLabel;
    }
    
    private static String replicate(char pChar, int pTimes){
        String lStr = "";
        for(int i = 0; i < pTimes; i++){
            lStr = lStr + pChar;
        }
        return lStr;
    }
    
    public SemanticExp getSemExp(){
        return mSemExp;
    }
    
    public void setSemExp(SemanticExp pSemExp){
        mSemExp = pSemExp;
    }
    
    public String getWord(){
        return mWord;
    }
    
    public void setWord(String pWord){
        mWord = pWord;
    }
    
    class InputExp{
        public InputExp(SemanticExp pSE, int pGroupIndex){
            mSE = pSE;
            mCurrentGroupIndex = pGroupIndex;
        }
        public SemanticExp mSE;
        public int mCurrentGroupIndex;
        
        public String toString(){
            return "ie(" + mSE.toString() + ", " + mCurrentGroupIndex + ")";
        }
    }
    
    private String mLabel;
    private String mWord = null; // Only set if mLabel is a word category.
    private LinkedList<ParseNode> mChildList;
    private int mDefaultStartIndex;
    private int mDefaultEndIndex;
    private SemanticExp mSemExp;
    private static int indentation = 0;
}
